# 这个模块主要用来决定一个链中的区块结构，以及他们是如何验证链是合法的、再以及不同形式的链的存储方式（比如有的节点可以仅作为验证节点存在，只需要存储哈希值即可）
# 此外还需要解决一下链冲突的问题，一般来说验证为合法最长链就行，这个函数暂时不做定制化
# 这里统一一下如下的接口规则,不同的规则以前缀加以区分(例如event_create_block和trans_create_block以及contr_create_block)：
# create_block(self, **kwargs): 创建上链, 后续的参数根据不同的情景需要是变长的，但是为了统一接口，因此整成定长的**kargs形式
# valid_proof(self, **kwargs): 验证区块的合法性
# valid_chain
# resolve_conflict
# TODO：0.0 代码编写
import time
import hashlib
from collections import OrderedDict
import requests
from globalVariables import MINING_DIFFICULTY
from utils.config_loader import config_loader, function_loader


class Block:
    '''
    一个块应当包含至少以下内容：块序号、时间戳、记录（允许为空）、先前块的哈希（这个作为区块链的防篡改来说，我们认为不可以取消）、以及其他内容（比如PoW中的nonce等）
    '''
    def __init__(self, block_number, timestamp, records, previous_hash, **kwargs):
        self.block_number = block_number
        self.timestamp = timestamp
        self.records = records
        self.previous_hash = previous_hash
        # 动态地将属性添加进类
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __str__(self):
        return str({
            "区块编号": self.block_number,
            "时间戳": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.timestamp)),
            "记录数量": len(self.records),
            "前块哈希": self.previous_hash,
            "本块nonce": self.nonce,
            "其它属性": "略"  # 根据不同的动态加载来修改
        })


def trans_create_block(self, nonce, previous_hash):
    """
    添加区块，这一块我目前不打算实现更高级的（比如只存储哈希）
    Add a block of transactions to the Blockchain
    """
    block = Block(block_number=len(self.chain) + 1,
                  timestamp=time.time(),
                  records=self.records,
                  previous_hash=previous_hash,
                  nonce=nonce)

    # Reset the current list of transactions
    self.transactions = []

    self.chain.append(block)
    return block


def event_create_block(self):
    pass


def contr_create_block(self):
    pass


def trans_valid_proof(self, block, difficulty=MINING_DIFFICULTY):
    """
    检测当前区块是否符合PoW要求
    Check if a hash value satisfies the mining conditions. This function is used within the proof_of_work function.
    """
    return self.hash(block)[:difficulty] == '0' * difficulty


def trans_valid_chain(self, chain):
    """
        检测整个链是否合法
        check if a blockchain is valid
    """
    last_block = chain[0]
    current_index = 1

    while current_index < len(chain):
        block = chain[current_index]
        # print(last_block)
        # print(block)
        # print("\n-----------\n")
        # Check that the hash of the block is correct
        if block.previous_hash != self.hash(last_block):
            return False

        # Check that the Proof of Work is correct

        # # Delete the reward transaction
        # transactions = block.records[:-1]
        # # Need to make sure that the dictionary is ordered. Otherwise we'll get a different hash
        # transaction_elements = ['sender_address', 'recipient_address', 'value']
        # transactions = [OrderedDict((k, transaction[k]) for k in transaction_elements) for transaction in transactions]

        if current_index != len(chain) - 1 and not self.valid_proof(block, MINING_DIFFICULTY):
            return False

        last_block = block
        current_index += 1

    return True


def resolve_conflicts(self):
    """
    节点间（关于最长链的）冲突的检测和解决
    Resolve conflicts between Blockchain's nodes
    by replacing our chain with the longest one in the network.
    """
    neighbours = self.nodes
    new_chain = None

    # We're only looking for chains longer than ours
    max_length = len(self.chain)

    # Grab and verify the chains from all the nodes in our network
    for node in neighbours:
        print('http://' + node + '/chain')
        response = requests.get('http://' + node + '/chain')

        if response.status_code == 200:
            length = response.json()['length']
            chain = response.json()['chain']

            # Check if the length is longer and the chain is valid
            if length > max_length and self.valid_chain(chain):
                max_length = length
                new_chain = chain

    # Replace our chain if we discovered a new, valid chain longer than ours
    if new_chain:
        self.chain = new_chain
        return True

    return False
